package game;

import java.awt.Color;
import java.util.ArrayList;
import java.util.List;

import players.Player;


public class HexBoard   {


  /** TempPlayers never make a move, i.e., they never respond to itsYourTurn( ). 
   *  They are used 
   *    (a) when checking for winners and
   *    (b) as the player occupying empty Cells.
   **/
  private static class TempPlayer extends Player {

    public TempPlayer() { /* */ }

    public TempPlayer(Color color) {
      super(color, false);
    }

    /** This is never called for a TempPlayer. But it must be completed. */
    @Override
    public Cell itsYourTurn(HexBoard hexBoard) {  return null;  }

  }

  private static final Color EMPTY_CELL_COLOR = new Color(230, 230, 230); 

  private static final TempPlayer EMPTY_CELL_PLAYER = new TempPlayer(EMPTY_CELL_COLOR);

  // Even though this array is indexed [0, 1], the TempPlayers are numbered 1 and 2.
  // getTempPlayer(i) = getTempPlayer[i-1]
  // The ONLY reference to tempPlayers should be in getTempPlayer( );
  private static final TempPlayer[] tempPlayers = {new TempPlayer(), new TempPlayer()};
  

  //////////////////////////  End of static declarations /////////////////////////////


  private final Cell [][] board ;

  // Even though this array is indexed [0, 1], the Players are numbered 1 and 2.
  // getPlayer(i) = players[i-1]
  // The ONLY reference to players should be in getPlayer( );
  // player(1) 1 plays left - right; player(2) plays top - bottom
  private final Player[] players;

  private final int size ;

  // These are the moves in the game.
  private final List<Cell> moves = new ArrayList<Cell>();

  // This is the list of cells to examine for the first empty Hex.
  // It is generated by generateMoveList( ).
  private List<Cell> emptyCellsList;

  private final HexGameFrame hexGameFrame;

  private Player winner = null;

  public final HexGame hexGame;

  private HexBoardImage hexBoardImage;


  private HexBoard (int size, Player[] players, HexGame hexGame) {
    this(size, players, null, hexGame);
  }
  
  
  public HexBoard (int size, Player[] players, HexGameFrame hexGameFrame) {
    this(size, players, hexGameFrame, null);
  }


  private HexBoard (int size, Player[] players, HexGameFrame hexGameFrame, HexGame hexGame) {
    this.size = size;

    this.board = new Cell[size][size];
    for (int i = 0; i < size; i++)
      for (int j = 0; j < size; j++)
        this.board[i][j] = makeEmptyCell(i, j);

    this.players = new Player[] {EMPTY_CELL_PLAYER, players[0], players[1]};
    
    this.hexGame = hexGame == null ? new HexGame(hexGameFrame, this) : hexGame;

    this.hexGameFrame = hexGameFrame;

    emptyCellsList = generateEmptyCellsList();
  }


  public void backOneMove() {
    undoMove();
    // Repaint the previous move to get the dot.
    setCell(moves.get(moves.size()-1));
    refreshEmptyCells();
  }


  @Override
  public HexBoard clone() {
    // Make a new instance of HexBoard object
    HexBoard theClone = 
      new HexBoard(this.size, new Player[] {getPlayer(1), getPlayer(2)}, hexGame) ;
    for (int i = 0 ; i < this.size ; i++)
      for (int j = 0 ; j < this.size ; j++)
        theClone.setCell(this.getCell(i, j).clone()) ;

    theClone.moves.addAll(moves); 

    return theClone ;
  }


  private void crossNeighbors(int x , int y){
    // Cross the whole spanning tree connected to (x, y) 
    // This is a helper method.

    Player player = getPlayerAt(x, y) ;
    if (player != getPlayer(1) && player != getPlayer(2) ) return ;

    setCell(x, y, getTempForPlayer(player)) ; //cross the cell itself
    crossNeighbors(x, y, player) ;
  }


  private void crossNeighbors(int x , int y, Player player){

    if (getPlayerAt(x, y) != player && getPlayerAt(x, y) != getTempForPlayer(player))
      return ;

    List<Cell> nbrs = getNeighbors(x, y, player );

    for (Cell nbr : nbrs) { // First cross all the neighbors
      int nbrX = nbr.getX();
      int nbrY = nbr.getY();
      setCell(nbrX, nbrY, getTempForPlayer(getPlayerAt(nbrX, nbrY))) ;
    }

    for (Cell nbr : nbrs) // Then cross neighbors' neighbors
      crossNeighbors(nbr.getX(), nbr.getY(), player) ;
  }


  @Override
  public boolean equals(Object other) {
    if (!(other instanceof HexBoard)) return false;
    HexBoard otherBoard = (HexBoard) other;
    for (int i = 0 ; i < getSize() ; i++)
      for (int j = 0 ; j < getSize() ; j++) {
        if (!getCell(i, j).equals(otherBoard.getCell(i, j))) {
          System.out.println(getCell(i, j) + " != " + otherBoard.getCell(i, j));
          return false;
        }
      }
    return true;
  }


  private List<Cell> generateEmptyCellsList() {
    List<Cell> emptyCells = new ArrayList<Cell>();
    emptyCells.add(new Cell(size/2, size/2, getEmptyCellPlayer())); //EMPTY_CELL_PLAYER));
    int i = 0;
    while (i < emptyCells.size()) {
      Cell move = emptyCells.get(i++);
      for (Cell neighbor : getCellNeighbors(move)) {
        if (!emptyCells.contains(neighbor)) emptyCells.add(neighbor);
      }
    }
    return emptyCells;
  }


  public Cell getCell(int x, int y) { 
    return valid(x) && valid(y) ? board[x][y] : null; 
  }


  private boolean valid(int z) {
    return z >= 0 && z < size;
  }


  public Cell getCell(Cell cell) {
    return getCell(cell.getX(), cell.getY());
  }


  public List<Cell> getCellNeighbors(Cell cell) {
    return getNeighbors(cell.getX(), cell.getY(), null);
  }


  private Player getEmptyCellPlayer() { return EMPTY_CELL_PLAYER; }


  public Cell getLastMove() { return moves.get(moves.size()-1); }
  
  
  public Player getLeftRightPlayer() { return getPlayer(1); }
  
  
  public String getLastNMovesString(int n) {
    StringBuffer moveRecord = new StringBuffer();
    int movesMade = moves.size();
    for (int i = movesMade-n; i < movesMade; i++) {
      moveRecord.append((moveRecord.length()==0?"":" -> ") + moves.get(i));
    }
    return moveRecord.toString(); 
  }
  


  /** Get the neighbors of <x, y>. If player is not null
   *  get only neighbors with the same player. Otherwise
   *  get all neighbors.
   */
  private List<Cell> getNeighbors(int x, int y, Player player) {
    List<Cell> neighbors = new ArrayList<Cell>() ;

    for (int i = -1; i <= 1; i++) {
      for (int j = -1; j <= 1; j++) {
        // There are 6 neighbors. On a rectangular board there would
        // be 8. Because the board is hexagonal, the missing ones are 
        // at <-1, -1> and <+1, +1>. Also, <0, 0> is the cell and not
        // a neighbor.
        if (i == j) continue;
        Cell cll = getCell(x+i, y+j); 
        // Must test for cll != null since getCell( ) returns null if
        // the requested cell is off the board.
        if (cll != null && (player == null || cll.getPlayer().equals(player))) 
          neighbors.add(cll);
      }
    }

    return neighbors;
  }

  public Cell getNextToLastMove() {
    return moves.size() > 1 ? moves.get(moves.size()-2) : null;
  }


  public Player getOtherPlayer(Player player) {
    int whichPlayer = whichPlayer(player);
    return getPlayer(whichPlayer == 0 ? 0 : 3 - whichPlayer);
  }


  public Player getPlayer(int i) { return players[i]; }


  private Player getPlayerAt(int i, int j) {
    return getCell(i, j).getPlayer();
  }


  public int getSize() { return size; }


  private Player getTempForPlayer(Player player) {
    return getTempPlayer(whichPlayer(player)); 
  }


  private Player getTempPlayer(int i) { return tempPlayers[i-1]; }


  public Player getTopBottomPlayer() { return getOtherPlayer(getLeftRightPlayer()); }


  public Player getWinner(){
    
    if (winner != null) return winner;
    
    HexBoard tempBoard = this.clone();

    // check if left-right player wins 
    for (int y = 0 ; y < this.size ; y++ )
      if (tempBoard.getCell(0, y).getPlayer() == getLeftRightPlayer())
        tempBoard.crossNeighbors(0, y);

    for (int y = 0 ; y < this.size ; y++ )
      if (tempBoard.getCell(this.size - 1, y).getPlayer() == getTempPlayer(1)) 
        return getLeftRightPlayer(); 

    // check if top-bottom player wins 
    for (int x = 0 ; x < this.size ; x++ )
      if (tempBoard.getCell(x, 0).getPlayer() == getTopBottomPlayer()) 
        tempBoard.crossNeighbors(x, 0);

    for (int x = 0 ; x < this.size ; x++ )
      if (tempBoard.getCell(x, this.size - 1).getPlayer() == getTempPlayer(2)) 
        return getTopBottomPlayer(); 

    return null;
  }


  public boolean isEmpty(Cell cell) {
    return getCell(cell).getPlayer() == EMPTY_CELL_PLAYER;
  }


  public boolean isLastMove(Cell cell) {
    return moves != null && !moves.isEmpty() && getLastMove().equals(cell);
  }


  private Cell makeEmptyCell(int i, int j) {
    return new Cell(i, j, EMPTY_CELL_PLAYER);
  }


  public void newGame() {
    refreshEmptyCells();
    new HexGameFrame(hexGameFrame.getPlyrMatchScores(), getPlayer(1), getPlayer(2), size); 
  }


  public void refreshEmptyCells() { 
    emptyCellsList = generateEmptyCellsList(); 
  }


  public HexBoard reverseAndTransposeBoard() {
    HexBoard rtBoard = clone();
    for (int i = 0 ; i < getSize() ; i++)
      for (int j = 0 ; j < getSize() ; j++) {
        rtBoard.setCell(new Cell(j, i, getOtherPlayer(getPlayerAt(i, j)) )) ; 
      }
    return rtBoard;
  }


  public Cell selectFirstEmptyCell() {
    // If the game isn't over, there must be an empty cell.
    while (true) {
      Cell cell = emptyCellsList.remove(0);
      if (isEmpty(cell)) return cell;
    }
  }


  public void setCell(Cell cell) {
    if (cell.isResignation()) winner = getOtherPlayer(cell.getPlayer());
    else board[cell.getX()][cell.getY()] = cell;
    // There are no images for cloned versions of the board.
    if (hexBoardImage != null && !cell.equals(hexGame.resignMove())) 
      hexBoardImage.paintCell(cell);
  }


  private void setCell(int x, int y, Player player) {
    this.board[x][y] = new Cell(x, y, player);
  }


  public void setHexBoardImage(HexBoardImage hexBoardImage) {
    this.hexBoardImage = hexBoardImage;    
  }


  public void setMove(Cell cell) {
    moves.add(cell);
    setCell(cell);
  }


  public void undoMove() {
    Cell move = moves.remove(moves.size()-1);
    setCell(makeEmptyCell(move.getX(), move.getY()));
  }


  public boolean undoableMove() { return moves.size()>1; }


  public int whichPlayer(Player player) {
    return players[1] == player ? 1 :
           players[2] == player ? 2 : 
           0;
  }



}



